RPL
RPL stands for Realsoft 3D Programming Language. It is a FORTH-like language integrated into the Realsoft 3D environment. It is a full featured programming language which provides you with power, flexibility and total control over all the features of Realsoft 3D. Using RPL, you can expand the functions of Realsoft 3D to fulfill any requirements you might have.
Because RPL is often used for extending the Realsoft 3D program, it has been designed to be as fast in its execution as possible. It is also capable of making direct access to the operating system. For these reasons, it is a relatively low-level language. However, considerable effort has been devoted to make it easy to learn with the intention of all users being able to make use of it.
This section is intended to introduce RPL programming to you as easily as possible; but some understanding of basic computer programming principles has been assumed. If after reading through the introduction and working through the tutorial section you do not feel you have a full understanding of how to use RPL then there are many good books on introductory programming. Any that directly relate to FORTH will provide the best assistance, and most of the examples should work in RPL.
Getting started
The first step of this tutorial is opening a scripting window. Select 'Windows/Scripting Window' from the pull-down menu. Realsoft 3D allows several programming languages to be used. Set the 'Language' field of the opened window to 'RPL'.
When trying the following examples, remember to enter the words EXACTLY as they appear in the examples - RPL is 'case sensitive'!
Example 1: Print a number.
Type the following text exactly as it is shown:
2 .
When you press Enter RPL will respond:
2
Note: You just asked RPL to print out the number '2'. The character '.' is a predefined function in RPL's vocabulary which prints out an integer value.
Example 2: Print some text.
Now enter the following line:
"hello world" PUTS
RPL responds:
hello world
Note: The word 'PUTS' (Put String) is another predefined function, which printed the text 'hello world'.
Before you test any further examples some explanation of how RPL operates is necessary.
Stacks
RPL is a stack oriented language. This means that RPL uses stacks to store operands, intermediate results and return addresses.
The stack works in Last In First Out (LIFO) manner, which is explained below in 'Parameter stack'.
Reverse polish notation
RPL uses postfix-notation, also known as Reverse Polish Notation (RPN).
In RPN the arguments, or operands, are pushed onto stack first and then an operation is applied to them. The result is left on top of the stack, and can be used in consecutive operations.
The conversion from algebraic notation (the way you write mathematical expressions on paper) to RPN is quite natural.
You start with the operands you would start with if you were doing the calculation manually, and then append an operator.
Then you take other operands and apply other operations until all the operands and operations are used. That may sound difficult but a couple of examples should clarify the point.
Example 3: Convert 12 + 5 + 2 to RPN.
1. Manually you would first add 5 to 12. Therefore you write:
12 5 +
Remember, the operands come first and then the operator.
2. Then you would add 2 to the result of the previous addition.
2 +
Note: Both '+' operators require two operands. The first addition uses 12 and 5 but the second '+' seem to have only one operand, the number 2. The second addition takes the result of the first addition as its second operand. How the result of the first operation is preserved will be explained in the section about the parameter stack.
So the whole expression in RPN is:
12 5 + 2 +
3. Finally you will use the '.' word to print the result. The RPL text becomes:
12 5 + 2 + .
And RPL responds by printing the result:
19
Example 4: Convert 2 + 5 * 7 to RPN.
1. First you multiply 5 with 7 and then add 2 to the result. So in RPL you enter:
5 7 * 2 + .
Parameter stack
When RPL scans input, whether it comes from the keyboard or from a file, it uses the parameter stack to store all operands it detects. This will be just called 'the stack' for brevity, when there's no risk of confusing it with the return stack.
There are three kinds of basic data types in the RPL system: integer, floating-point and address. The address operands can be addresses of RPL words, or addresses of variables (integer or floating-point) or generic addresses, addresses to any kind of value you want. All the addresses are handled in the same way. That is to say that RPL makes no distinction between addresses of different types of items.
The stack operates on the basis of the last operand in will be the first out. This is the LIFO principle as mentioned. If you picture the stack as a pile of bricks (or any other neatly stackable material), you put new bricks on the top of the pile and also take them off from the top.
So how are operands put onto the stack? Just enter them, and they are 'pushed' onto the stack so that the last entered number will be the top-most one.
In Example 1, you pushed one operand onto the stack (number 2). Then the command '.' pulled it off and printed it in your RPL window.
Now push several operands onto the stack. Try the following:
1
2
3
You pushed three numbers onto the stack. Print them all out by entering three '.' commands and RPL responds:
3
2
1
Try to enter '.' command once more time and RPL will respond:
Stack empty
There were no more operands left on the stack for RPL to print.
You get the same error message if you, for whatever reason, try to pull a value from the stack when none are available. This would occur if you had two integers on the stack and entered the '+' word twice. The second '+' has only one stack item and the error message is given.
Now examine what happened to the stack for example 3. Diagrams will be used to illustrate what is happening with the stack as each step is interpreted.
1. Suppose you start with an empty stack.
2. The RPL statement '12 5 + 2 + .' was scanned by the RPL interpreter.
3. First, 12 was pushed to stack.
+-------+ <- top of stack
| 12 |
+-------+
4. Then 5 was pushed on stack
+-------+ <- top of stack
| 5 |
+-------+
| 12 |
+-------+
5. The '+' was scanned, which, because it is an executable word - not an operand - is executed. The word '+' pops (takes off) the first two operands from the stack, then adds them and pushes the result back onto the stack.
+-------+
| 17 |
+-------+
6. Then 2 was pushed on stack
+-------+
| 2 |
+-------+
| 17 |
+-------+
7. The last '+' was scanned, the top two stack items are added, resulting in the following stack situation:
+-------+
| 19 |
+-------+
8. Finally the '.' was scanned and interpreted and the value is printed leaving the stack in its initial empty state. Note: RPL words must be separated from each other and their operands by either an empty space or Enter, but using Enter also passes the current line to the RPL system and it will be interpreted immediately. So the example above could also have been entered as:
12
5
+
2
+
.
Data types
There are three kinds of basic data types in the RPL system: integer, floating-point and string. In order to enter values on the stack, you separate them by Space or Enter just as you have done in the examples above.
Integers can contain only integral values, such as -3, 0 and 12, while floating-point numbers can be used for representing decimal numbers like -1.2, 0.12 and 1243.1.
Integers
Integer literals begin with a optional sign (+ or -) and contain only decimal digits. So far you have only used integer arithmetic, with integer literals and their corresponding integer operator words.
Floating-points
Floating-point literals are similar to integer literals but they contain a decimal point or an exponent separator or both. The exponent separator can be either 'e' or 'E'. The exponent can then contain an optional sign.
As you would expect, arithmetic operations can also be carried out with floating-point values. There are separate functions to carry out these operations:
F+ - add two floating-point values F- - subtract F* - multiply F/ - divide F. - print Fxx - etc.
Example 5: Enter the following text:
100.0 0.5 F* F.
RPL replies:
50.000000
It did that because you pushed two floating-point values onto the stack then multiplied them as floating-points and finally printed out the return value from the 'F*' word, as a floating-point.
Example 6: Now try the following:
123
F.
and RPL responds with:
123.000000
This demonstrates an important principle of RPL. It knows the data type of each stack value and the data type it requires for each operand of a word and handles them accordingly. In Example 6 above, when the command 'F.' pulled the integer value 123 off the stack, it converted the value to the required type before it tried to use it. This also works if words requiring integer operands receive floating-point values.
Example 7: When you enter:
6.8
.
RPL will respond with:
6
The RPL word '.' works with integers, so it had to convert the floating-point value it pulled from the stack to an integer before it printed it. Because integers cannot contain any fractional part the value was truncated.
Strings
String literals begin and end with a double quote character. If a double-quote is wanted in a string literal, a single-quote sign should precede it.
For example:
"This string contains a '"."
Will be interpreted as:
This string contains a ".
If a single-quote is required then two must be entered consecutively. For example:
"This string contains a ''."
Will be interpreted as:
This string contains a '.
RPL handles strings slightly differently from the way the integers and floating-points are treated. With integer and floating-point literals the numerical value is pushed on the stack, and with string literals the address of the first character of the string is placed on the stack.
You have already encountered the string printing word 'PUTS'. Let's look at how it actually operates.
Consider the RPL text:
"Hello, this is a string" PUTS
1. When the string "Hello, this is a string" was typed, RPL did NOT push the entire string onto the stack! Instead the address of the string was pushed. The memory for the string itself was allocated from elsewhere.
2. The word 'PUTS' then pulls the address out off the stack and copies the string from that address to the window.
Every character has a corresponding value, which is used for representing the character, this is called the ASCII code. For example, the ASCII code for the character '!' is 33. So, when RPL stores the character '!', it actually stores the value 33. The RPL word 'EMIT' can be used for printing out characters by supplying an ASCII code for them as operands.
Example 8. Enter the following:
33 EMIT
and RPL replies:
!
So, you can store a simple string onto the stack and print it out using the EMIT function.
Example 9: Enter
76 80 82 EMIT EMIT EMIT
and RPL replies:
RPL
Finally, you can investigate one property of string literals that might not be immediately obvious. As you just learned, entering a string caused RPL to allocate the memory for the string and push the address of it onto the stack. To be precise, the address of the first character is pushed onto the stack. This means that is possible to use arithmetic operations to select part of a string.
Example 10:
1. Type the following string:
"Hello again!"
Note: The first character of 'again!' is the 6th in the whole string.
2. Enter:
6 +
Note: You have now added 6 to the address of the first character ('H') of the string.
3. Finally, type:
PUTS
and RPL replies
again!
Some care is needed when using this 'address arithmetic'; if you make an error and change the address so that it is outside of the original string data structure, then the results are quite unpredictable. In the worst case you could crash the system.
Stack manipulation words
There are a number of words that change the order of the stack items. The 'SWAP' word, as its name implies, takes two values off the stack top and puts them back in reverse order.
Example 11:
1 5 SWAP
after 1 after 5 after SWAP
+-------+ +-------+ +-------+
| 1 | | 5 | | 1 |
+-------+ +-------+ +-------+
| 1 | | 5 |
+-------+ +-------+
'SWAP' is only needed if the order of the operands affects the result. It is unnecessary when using words such as '+' and '*' among others.
Quite often there is more than one way to enter an algebraic expression in RPL, as the following example shows.
Example 12: Calculate the value of 28 / (2 + 4 * 3)
1. You start from within the parentheses as you would when calculating manually:
4 3 * 2 + 28 SWAP /
after 4 after 3 after * after 2
+-------+ +-------+ +-------+ +-------+
| 4 | | 3 | | 12 | | 2 |
+-------+ +-------+ +-------+ +-------+
| 4 | | 12 |
+-------+ +-------+
after + after 28 after SWAP after /
+-------+ +-------+ +-------+ +-------+
| 14 | | 28 | | 14 | | 2 |
+-------+ +-------+ +-------+ +-------+
| 14 | | 28 |
+-------+ +-------+
2. Or, start from the left:
28 2 4 3 * + /
after 28 2 4 3 after * after + after /
+-------+ +-------+ +-------+ +-------+
| 3 | | 12 | | 14 | | 2 |
+-------+ +-------+ +-------+ +-------+
| 4 | | 2 | | 28 |
+-------+ +-------+ +-------+
| 2 | | 28 |
+-------+ +-------+
| 28 |
+-------+
This illustrates that although you can usually manage without 'SWAP', it can quite often make it easier to enter your expressions in an understandable way.
The next useful word for manipulating the stack, is 'DUP', which, as its name suggests, duplicates the top stack value. This is useful when you need an entered value or intermediate result more than once.
Example 13: Calculate (3 + 7 * 2) / 3 + (3 + 7 * 2) * 4.
You can convert it to RPL and get
3 7 2 * + 3 / 3 7 2 * + 4 * + .
or you can use DUP and get
3 7 2 * + DUP 3 / SWAP 4 * + .
You can try these out and see for yourself that they give the same result.
To get a copy of the second stack item you use the word OVER.
Example 14: To calculate 3 + 3 * 7 you can enter:
3 7 OVER * + .
If you need get a copy of a value deeper in the stack you use the PICK word.
Example 15: To get a copy of the fourth value on the stack you enter:
4 PICK
Note: The count of stack items (i.e. 4) does not include the count itself although it will be on the stack top when 'PICK' is applied. In the previous example '4 PICK' will copy to the stack top the value, which was the fourth stack item before 4 was entered.
'ROT' rotates the three topmost stack values so that the third item will become the top item, and the first and the second item (counting from the stack top) will be the second and the third stack item respectively.
Example 16: Push 3, 5 and 7 onto the stack and then apply ROT and see how it changes the stack.
3 5 7 ROT
after 3 after 5 after 7 after ROT
+-------+ +-------+ +-------+ +-------+
| 3 | | 5 | | 7 | | 3 |
+-------+ +-------+ +-------+ +-------+
| 3 | | 5 | | 7 |
+-------+ +-------+ +-------+
| 3 | | 5 |
+-------+ +-------+
Note: You can use the '.S' word to print the contents of the whole stack without removing any values to see how this example progresses.
The word ROLL is used to rotate a given number (n) of stack items so that the n'th stack item becomes the stack top item and all the items between the first and the n'th item are moved one position deeper in the stack.
Finally there is also a word for popping a value off the stack without printing it. The word is 'DROP'. You will need it later when you examine conditional execution.
Compiling new words
The examples so far have only demonstrated that RPL can be used as 'a slightly peculiar calculator'. If it is to be used as a programming language, RPL needs to be able to store sequences of operations for repeated use or which need to be executed by another part of the program. Many programming languages call these sequences functions, procedures or subroutines. The term 'word' from the FORTH programming language is adopted for RPL.
A word is a collection of RPL code, which can be recognized by its name. This code can consist of literals, references to variables and constants, and references to other words.
A definition of a word begins with a colon ':', continues with the name of the new word, and ends with a semicolon ';'. When the RPL interpreter scans the colon word, a new entry in the vocabulary is started. The name of the word and the type of entry (in this case a 'word definition') are stored. Everything between the name and the ending semicolon is compiled as the word's definition.
When a previously defined word is given as part of the definition, a reference to it is stored. When a literal is given, its type (integer, float or string) and its value are stored in the entry. When a named constant or a variable (See: Constants and Variables) is given, a reference to its own entry is stored. In summary, the vocabulary entry for a word is made of a name, the word type and a set of literal values and references to previously defined words.
The following examples should help to explain:
Example 17: Define a word that prints out a specific string. Enter:
: Prompt
"PROMPT_TEXT" PUTS
;
Remember, there has to be at least one space between ':' and the name for the new word, because the ':' is itself a word.
User-defined words are used just like any pre-defined word. To use them you just need to enter their name.
Example 17b: Use the word 'Prompt' defined above. Enter:
Prompt
RPL will print:
PROMPT_TEXT
User defined words would be quite useless if you couldn't pass information to them to control their operation. However, as mentioned above, user-defined words are indistinguishable from the built-in words. This means that passing operands to them is carried out using the parameter stack in exactly the same way as you have passed operands to words since Example 1! Now test this by defining your own integer printing word.
Example 18: A new integer printing word.
: MyPrint
.
;
Call it:
10 MyPrint
and RPL responds:
10
Or call it as follows:
10 20 100 MyPrint MyPrint MyPrint
and the reply will be:
100 20 10
Now create a function, which prints numbers out twice. To do this, you have to use the 'DUP' word mentioned previously.
Example 19: Print number twice as an integer.
: My2Print
DUP
.
.
;
Now call it:
10 My2Print
and it will reply:
10 10
or if you call it as follows:
10 My2Print 20 My2Print 100 My2Print
RPL responds with:
10 10 20 20 100 100
Before the final example in this section you should learn how to use 'comments' so you can add notes to your program to inform others how it works (or remind yourself at a later date). The word '(' tells RPL to skip all following text up to the next ')' command or until the end of the current line. Because '(' is a word, you have to separate it from the text that follows with a space.
Comment examples:
( this is a comment )
( these are also
( legal comments
(this produces syntax error)
( this is an illegal
comment too )
Finally, let's create a function, which takes three numbers as an input and returns the integer average of them.
Example 20: Calculate the average of three numbers.
: MyAver ( add 3 numbers and divide result by 3 )
+ + 3 /
;
If you call it as follows:
10 20 30 MyAver .
RPL returns:
20
or try:
10 20 120 MyAver .
and the response will be:
50
Constants and variables
RPL has a mechanism to store and fetch values not only from the stack but also in 'constants' and 'variables'. A 'constant' is a named entity whose value is set when it is defined, and the value can be referenced but not changed. A 'variable' is a named entity whose value can be referenced and used for either input or output.
Constants and variables must be defined outside word definitions. When a constant is defined, its value is taken from the stack top. When you later want to reference a constant you enter its name.
Constants
Constants are used for storing values that won't change. Although this might seem to be the same as using literals, it is good programming practice to use constants instead of literal values. Constants should be descriptive, and this makes your code much easier to read by others as well as yourself. When a constant is referrenced by its name, the value is placed on the stack.
Example 21: Define integer constant 'DaysInAWeek' with the value 7. Enter:
7 CONSTANT DaysInAWeek
Then entering:
DaysInAWeek .
prints out:
7
Example 22: Define the value for the floating-point constant 'PI'.
3.1415927 FCONSTANT PI
Variables
When a numeric variable is defined, its initial value is set to zero. For each numeric type (integer and floating-point) there are two words that are used to access this value. For integer variables these words are:
- FETCH - takes the address of an integer variable and returns the value of that variable on the stack.
- STORE - takes two operands: the address of an integer variable and a new value for the variable, and changes the value of the variable. The address must be the top item on the stack and the new value the second.
The address of a variable is pushed onto the stack when it is referred to, not the value as with constants.
Example 23: Define an integer variable COUNTER and store 17 into it, then check its value.
VARIABLE counter
17 counter STORE
counter FETCH .
Examine what happened during this example step by step:
VARIABLE counter
1. Space was reserved for one integer value, to be accessed by the name 'counter'.
17 counter STORE
2. The number 17 was pushed onto the stack.
3. The address of the variable was fetched and pushed onto the stack.
4. The word 'STORE' was executed, which takes the address of a variable off the stack and assigns the second operand to the value at that address. In a way, the name of a variable is the address of its value.
counter FETCH .
5. The address of the 'counter' variable was again pushed onto the stack.
6. The word 'FETCH' was executed, which moves the contents from the given address onto the stack.
7. The '.' word was executed, which printed the value from the stack to your window.
There are corresponding words for floating-point variables: 'FVARIABLE', 'FSTORE' and 'FFETCH'.
Variables and constants can be used inside or outside of a word definition. When used inside a word definition, only a reference to the constant or a variable is compiled in the word's dictionary entry.
The value of the constant or the address of the variable is fetched only when the word is executed, not when it is defined.
Although the stack is object oriented and can automatically convert between numeric types, the internal storage of integer and floating-point variables is different. This means that words that operate on floating-point variables, i.e. FSTORE and FFETCH should not be used to access integer variables. The same applies to floating-point variables and the words for integer variable access (STORE and FETCH).
Flow control
Every programming language has to be able to alter the program flow when certain conditions are met. It is also necessary to be able to repeatedly execute a section of code. RPL has addressed these needs by including a versatile set of words into its basic vocabulary for controlling program flow. The flow control words can only be used from within word definitions.
Conditional execution
It is often necessary to decide whether a section of code should be executed or not. This is called conditional execution. The most basic conditional execution structure is 'IF..ENDIF'. The 'IF' word takes the stack top value and uses it to make a decision about how the execution is to proceed; this decision value is called a 'flag'. In RPL a flag value is said to be 'FALSE' if it is equal to zero and 'TRUE' if it has any other value. If the flag is TRUE, the words between IF and the corresponding ENDIF are executed, otherwise execution skips to the word that is immediately after the ENDIF.
Comparisons
To put a flag on the stack ready for IF, it is useful to use words that compare stack items and as a result push a flag on the stack. Built into RPL are the following comparison words:
Integer Float
------------------------------------
= F= - is equal
< F< - is less
F< - is greater
= F= - greater or equal
<= F<= - less or equal
< F< - not equal
Each of these takes two stack values, compares them, and places a flag onto the stack whose value depends upon the result of the comparison.
Example 24: Compare 2 and 3 to see which is less.
2 3 <
The second stack item is compared to the first stack item; the flag pushed on the stack indicates whether 2 is less than 3. A value of 1 means 'TRUE', and a 0 means 'FALSE'.
after 2 after 3 after <
+-------+ +-------+ +-------+
| 2 | | 3 | | 1 |
+-------+ +-------+ +-------+
| 2 |
+-------+
The flag states that two is less than three (as you may have guessed).
IF..ENDIF structure
This is the simplest form of conditional execution structure.
Example 25: Unpredictable program execution.
: Maybe
RANDOM 0.5 F<
IF
"Yes, " PUTS
ENDIF
"Thats it" PUTS
;
Try executing 'Maybe' several times and examine its response.
Note: This introduces the word 'RANDOM' which returns a random floating-point number between 0.0 and 1.0 when executed.
The previous example tests whether the return value from 'RANDOM' is less than 0.5. If it is, it prints out the text "Yes, Thats it" otherwise it just prints "Thats it". This is the explanation of the previous example. When the 'Maybe' is executed:
- Executing 'RANDOM' pushes a floating-point value onto the stack
- 0.5 is pushed onto the stack
- The 'F<' word takes two (floating-point) operands, compares 'random < 0.5' and pushes the flag back onto the stack.
- The 'IF' function takes the flag off the stack and if it is non-zero, then the '"Yes, " PUTS' text is executed; otherwise execution skips to '"Thats it" PUTS' after the 'ENDIF'.
Note that 'IF' and 'ENDIF' words must be contained within a word definition. Remember also that every 'IF' needs one corresponding 'ENDIF'.
IF..ELSE..ENDIF structure
Now you can define a word that prints only positive values, negative values are just disposed of.
Example 26: Word to print only positive values.
: .Pos
DUP 0 ( compare operand to zero )
IF
. ( print it if greater )
ENDIF
;
If you enter:
4 .Pos
RPL returns:
4
So that seems to work. But what happens if you enter:
-2 4 3 -1 .Pos .Pos .Pos .Pos
It doesn't produce:
3 4
but nothing at all. The problem is that the word does not get rid of the negative values, so you called .Pos four times with the same value of -1 on the stack.
You need to take some action if the value on the stack is not greater than zero. This is handled using an 'ELSE' branch where the negative value will be dropped off the stack.
Example 26b: A correct way to print only positive values.
: .Pos
DUP 0
IF
.
ELSE
DROP
ENDIF
;
This demonstrates another form of the IF structure, the IF..ELSE..ENDIF structure. If you now enter
-2 4 3 -1 .Pos .Pos .Pos .Pos
you get
3 4
as originally intended.
Inverting flags
As well as the integer and floating-point comparison words, there is another useful word for use in conditional structures. The 'NOT' word can be used for reversing the result of a comparison. For example:
10 20 < .
and:
10 20 = NOT .
Both print out the same value of '1' because the condition is TRUE.
Nesting conditionals
Conditional structures can also be nested. This means that they can be stacked inside each other providing that for each 'IF' of 'IF..ELSE' there is a corresponding 'ENDIF'.
Example 27: Use of a nested IF..ELSE..ENDIF structure.
: CheckIt
DUP ( duplicate the operand so you don't lose it
0 = ( compare it with zero
IF
"Zero " PUTS
DROP( dispose of duplicate because it won't be needed
ELSE
0 ( make comparison 'is greater than' with duplicate
IF
"Greater " PUTS
ELSE
"Less " PUTS
ENDIF
ENDIF
;
Now call it as follows:
-2 CheckIt
3.7 CheckIt
0 CheckIt
you should get the following response:
Less
Greater
Zero
Loops
In this section, the RPL words for branching back to repeat sections of code several times will be explained. This type of control structure is called a 'loop'. There are two basic kinds of loop: definite loops, which execute a specific number of times unless interrupted, and indefinite loops, whose repeated execution is controlled conditionally in much the same way as the IF..ENDIF structure.
Definite loops
The basic RPL control structure for executing definite loops is 'DO..LOOP'. You specify beginning and ending values, called the limits, for a 'loop variable'. The ending value is first pushed onto the stack, then the beginning value, before the word 'DO'. Then you put the words to be repeated, and the structure ends with the word 'LOOP'.
Example 28: Printing a string 100 times.
: DoLoop
100 0 DO
"RPL loops in style!" PUTS
LOOP
;
DoLoop
The 'loop variable', or index, starts with the starting value, and is incremented by 1 at the end of each iteration of the loop. When it reaches the ending value the loop is exited. There is a word for retrieving the value of the index. Its name is 'I', and it should only be used between 'DO' and 'LOOP'. It puts the current value of the loop variables on the stack.
Example 29: Count up from 1 to 9 and print the index.
: OneToNine
10 1 DO
I .
LOOP
;
OneToNine
Prints:
1 2 3 4 5 6 7 8 9
The 'DO..LOOP' always starts at a value and counts up by one until it reaches the ending value. If you want to increment the index by a value other than 1, you can use another form of the definite loop: the 'DO..+LOOP'. The '+LOOP' word takes an operand from the stack and adds it to the index. If the ending value is greater than the beginning value, then the loop is exited if the loop variable becomes greater than or equal to the ending value.
Example 30: Count from 0 to 10 in twos, printing the index.
: UpInTwos
10 0 DO
I .
2 +LOOP
;
UpInTwo
Prints:
0 2 4 6 8
If the ending value is less than the beginning value, then the 'DO..+LOOP' is exited when the loop variable becomes less than or equal to the ending value.
Example 31: Count down by one and print the index.
: DownByOnes
DO
I .
-1 +LOOP
;
0 5 DownByOnes
Prints:
5 4 3 2 1
Note: In this example the word 'DownByOnes' takes its operands from the stack, so you must supply them when you call the word.
Both forms of the 'DO' loop execute at least once since the conditional check is made when 'LOOP' or '+LOOP' is executed. It is possible to terminate a definite loop prematurely. The execution of word 'LEAVE' causes the loop to terminate at the next 'LOOP' or '+LOOP'.
Example 32: Print the squares of numbers until the square 50.
: SquareLoop
10 0 DO
I DUP * ( index squared )
DUP
50 IF ( compare the square to 50 )
DROP
LEAVE
ELSE
.
ENDIF
LOOP
;
When executed 'SquareLoop' will print the squares of 'I' until they exceed 50.
Indefinite loops
Unlike definite loops, an indefinite loop does not terminate after a specified number of iterations. It terminates when a condition is met, if ever.
All indefinite loops, just like definite loops, must lie entirely within a single definition. The structures of the indefinite loops are:
- BEGIN..WHILE..REPEAT
- BEGIN..AGAIN
In each of these loops, 'BEGIN' marks the beginning of the loop body, which extends to the terminating word 'UNTIL', 'REPEAT', or 'AGAIN'. In the 'BEGIN..UNTIL' loop a flag is tested and removed from the stack at the end of each repetition of the loop. If the flag is TRUE, the loop terminates. Otherwise the loop repeats from the first word following 'BEGIN'. Since the test is made at the end of the loop, the loop will always be executed at least once.
Example 33:
: CountDown1
BEGIN
DUP . ( print it out )
1 - ( decrement by one )
DUP 0 <= ( if TRUE, terminate the loop )
UNTIL
DROP
"PANG" PUTS
;
10 CountDown1
The 'BEGIN..WHILE..REPEAT' form of indefinite loop first executes the code between 'BEGIN' and 'WHILE', and then a flag is tested. If the flag is TRUE, the words between 'WHILE' and 'REPEAT' are executed; then execution returns to 'WHILE'. If the flag is FALSE, then execution skips to after the 'REPEAT'.
Example 34:
: CountDown2
BEGIN
1 - ( decrement by one )
DUP ( if 0, terminates the loop )
WHILE
DUP .
REPEAT
"Bang" PUTS
DROP
;
10 CountDown2
The third form of indefinite loop has the form 'BEGIN..AGAIN', which executes forever unless either the word 'QUIT' or 'EXIT' is executed.
'QUIT' terminates execution, empties all stacks and returns control to the interpreter, while 'EXIT' exits the current word.
Example 35:
: CountDown3
BEGIN
DUP .
1 -
DUP NOT
IF
EXIT
ENDIF
AGAIN
;
10 CountDown3
Nested loops
As with conditional structures, all the loop forms can be nested. In examples 29 to 32 you used the RPL word 'I' to obtain the value of the loop variable; when you nest 'DO..LOOP' structures the words 'J' and 'K' can be used to copy the indices of the second and third outer loops, respectively.
Example 36: Print a multiplication table.
( print a carriage return and a line feed: )
: CR 13 EMIT 10 EMIT ;
: multTable
6 1 DO
11 1 DO
I J * ( multiply indices
. ( and print the product
LOOP
CR
LOOP
;
multTable
Will print:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
This looks a little untidy so you can tidy it up by adding a conditional to print a space.
Example 36b: Print a neat multiplication table.
: multTable
6 1
DO
11 1
DO
I J * ( multiply indices
DUP ( duplicate product for testing
10 <
IF ( if<10 then print a space first
" " PUTS
. ( now print the product
ELSE ( two digits in the product
. ( so just print it
ENDIF
LOOP
CR
LOOP
;
multTable
Note: It is a convention to indent flow control structures as shown in the previous two examples. This does not make any difference to the RPL system but, like comments, makes your programs easier to read and understand.
Words and the vocabulary
The next example, as well as being your most comprehensive RPL program to date, also demonstrates an important feature of the RPL language. If you define a word with the same name as a previously defined word then the latest definition is used. You can list the whole vocabulary of your current RPL window by entering the word 'VLIST'. Before you do this, size the window to make it a decent size, the vocabulary is quite long.
The whole vocabulary, including the built-in words, will be printed to the RPL window in chronological order with the latest words printed first. You should see that the last two words are both 'multTable'.
It is possible to remove word definitions from the vocabulary using the word 'FORGET'.
Example 37: Remove neat 'multTable' from vocabulary.
FORGET multTable
If you now execute 'multTable' you will get the original untidy version.
'FORGET' actually removes ALL the words after the specified word, inclusive. So, assuming you have worked through all the examples, if you enter:
FORGET Prompt
Then all the words you have defined during this tutorial will be deleted.
It is important to note that the RPL system makes NO DISTINCTION between the built-in words and words defined interactively or by loading a file (see next section). This means that if you create a word definition using the name of one of the built-in words then your new definition will replace the original. This can be quite significant if you replace an important or frequently used word.
You can of course recover the situation using 'FORGET'; which raises another important point: you can delete built-in words just as easily as your own. This makes it possible to create a situation where RPL does not have the vocabulary to do anything! Closing the window and opening a new one enables you to start with a fresh RPL environment.
Loading a file
Entering RPL definitions directly to the window is acceptable for the short examples so far in this tutorial, but it is hard to recover from mistakes without having to completely re-enter a definition. Using a text editor you can write any RPL code to a file and then load that file into your RPL window.
Example 38: Write the following RPL program using your favorite text editor and save it as 'test.rpl'
"loading ... " PUTS
: CR
13 EMIT 10 EMIT
;
: Hello
"Hello world" PUTS CR
;
"Done" CR
Now type the following program:
"test.rpl" LOAD
RPL responds:
loading ... Done
Now the words CR and Hello are defined and you can call them. Type:
Hello
and RPL responds
Hello world
Using multiple scripting windows
You open a RPL window by selecting the pull down menu 'Windows/Scripting Window'. You can have many RPL windows if you want. You can use Shift Delete and Shift Insert to cut and paste text to and from the windows clipboard and the cursor keys can be used to edit your text and even fetch previously entered text from the window buffer.
Each window is separate so that whatever words are defined in one window, cannot be used in other windows. Of course you can define identical words in different windows. The stacks are also specific to each window.